/* Filename: relay.c
 * No.106-108.高级IO-select & select实例 & poll实例
 * Description: 中继
 * $ sudo ./relay
 * $ <ctrl+alt+f11>
 * $ hello<CR>
 * $ <ctrl+alt+f12>
 * $ world<CR>
 * $ <ctrl+alt+f11>
 * $ <ctrl+alt+f1> 或 <ctrl+alt+f7>
 * Last modified: humble 2020-09-05 16:35:29
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <sys/select.h>

#define USE_NONE (0)
#define USE_SELECT (1)
#define USE_POLL (2)

#define demo (USE_POLL)

#define TTY1 "/dev/tty11"
#define TTY2 "/dev/tty12"
#define BUFSIZE (1024)

typedef enum{
    STATE_R = 1, //读
    STATE_W, //写
#if (demo == USE_SELECT) || (demo == USE_POLL)
STATE_AUTO, //分界线，上面是事件条件推fsm，下面是无条件推fsm
#endif
    STATE_Ex, //异常终止
    STATE_T  //结束
}state_t;

typedef struct{
    state_t state;
    int sfd;
    int dfd;
    char buf[BUFSIZE];
    int len;
    int pos;
    char *errstr;
}fsm_t;

static void usage(void);

static void fsm_driver(fsm_t *fsm)
{
    int ret;
    switch(fsm->state){
        case STATE_R:
            fsm->len = read(fsm->sfd, fsm->buf, BUFSIZE);
            if(fsm->len == 0){ //读到文件末尾或者socket已关闭
                fsm->state = STATE_T;
            }
            else if(fsm->len < 0){
                if(errno == EAGAIN){
                    fsm->state = STATE_R;
                }
                else{
                    fsm->errstr = "read()";
                    fsm->state = STATE_Ex;
                }
            }
            else{
                fsm->pos = 0;
                fsm->state = STATE_W;
            }
            break;
        case STATE_W:
            ret = write(fsm->dfd, fsm->buf + fsm->pos, fsm->len);
            if(ret < 0){
                if(errno == EAGAIN){
                    fsm->state = STATE_W;
                }
                else{
                    fsm->errstr = "write()";
                    fsm->state = STATE_Ex;
                }
            }
            else{
                fsm->pos += ret;
                fsm->len -= ret;
                if(fsm->len == 0){
                    fsm->state = STATE_R;
                }
                else{
                    fsm->state = STATE_W;
                }
            }
            break;
        case STATE_Ex:
            //printf("%s\n", fsm->errstr);
            perror(fsm->errstr);
            fsm->state = STATE_T;
            break;
        case STATE_T:
            /* do sth */
            break;
        default:
            /* do sth */
            abort();
            break;
    }
}

#if (demo == USE_SELECT)
static int max(int a, int b)
{
    return a > b ? a : b;
}
#endif

static void relay(int fd1, int fd2)
{
    int fd1_save, fd2_save;
    fsm_t fsm12, fsm21;
#if (demo == USE_SELECT)
    fd_set rset, wset;
#elif (demo == USE_POLL)
    struct pollfd pfd[2];
#endif

    fd1_save = fcntl(fd1, F_GETFL);
    fcntl(fd1, F_SETFL, fd1_save|O_NONBLOCK);

    fd2_save = fcntl(fd2, F_GETFL);
    fcntl(fd2, F_SETFL, fd2_save|O_NONBLOCK);

    fsm12.state = STATE_R;
    fsm12.sfd = fd1;
    fsm12.dfd = fd2;

    fsm21.state = STATE_R;
    fsm21.sfd = fd2;
    fsm21.dfd = fd1;

#if (demo == USE_POLL)
    pfd[0].fd = fd1;
    pfd[1].fd = fd2;
#endif

    while(fsm12.state != STATE_T || fsm21.state != STATE_T){
#if (demo == USE_NONE)
        //忙等，浪费CPU
        fsm_driver(&fsm12);
        fsm_driver(&fsm21);
#elif (demo == USE_SELECT)
        //布置监视任务(以事件为单位)
        FD_ZERO(&rset);
        FD_ZERO(&wset);
        if(fsm12.state == STATE_R){ FD_SET(fsm12.sfd, &rset); } //设置关心读事件
        if(fsm12.state == STATE_W){ FD_SET(fsm12.dfd, &wset); } //设置关心写事件
        if(fsm21.state == STATE_R){ FD_SET(fsm21.sfd, &rset); }
        if(fsm21.state == STATE_W){ FD_SET(fsm21.dfd, &wset); }
#elif (demo == USE_POLL)
        //布置监视任务(以文件描述符为单位)
        pfd[0].events = 0; //先清零之前的事件
        if(fsm12.state == STATE_R){ pfd[0].events |= POLLIN; } //设置关心读事件
        if(fsm21.state == STATE_W){ pfd[0].events |= POLLOUT; } //设置关心写事件

        pfd[1].events = 0; //先清零之前的事件
        if(fsm21.state == STATE_R){ pfd[1].events |= POLLIN; }
        if(fsm12.state == STATE_W){ pfd[1].events |= POLLOUT; }
#endif

#if (demo == USE_SELECT)
        //监视
        if(fsm12.state < STATE_AUTO || fsm21.state < STATE_AUTO){ //只监视处于STATE_R和STATE_W的
            //超时参数用NULL表示忙等
            if(select(max(fd1, fd2) + 1, &rset, &wset, NULL, NULL) < 0){
                if(errno == EINTR){ continue;  /* 发生假错又得重新布置任务了*/ }
                perror("select()");
                exit(1);
            }
        }

        //查看监视结果
        //只关心读写，那fsm进入STATE_T就没法往下推了，判断STATE_AUTO让其往下推
        //如果fsm12.sfd可读与fsm12.dfd可写两件事同时发生，那只能处理其中一件？
        //如果fsm12.state是STATE_R，但只发生可写，那fsm_driver(&fsm12)就会阻塞read()?
        if(FD_ISSET(fsm12.sfd, &rset) || FD_ISSET(fsm12.dfd, &wset) || fsm12.state > STATE_AUTO){
            fsm_driver(&fsm12);
        }
        if(FD_ISSET(fsm21.sfd, &rset) || FD_ISSET(fsm21.dfd, &wset) || fsm21.state > STATE_AUTO){
            fsm_driver(&fsm21);
        }
#elif (demo == USE_POLL)
        //监视
        if(fsm12.state < STATE_AUTO || fsm21.state < STATE_AUTO){
            //2个文件描述符，-1表示阻塞
            while(poll(pfd, 2, -1) < 0){
                if(errno == EINTR){ continue; /* 发生假错不用重新布置任务pfd了 */ }
                perror("poll()");
                exit(1);
            }
        }

        //查看监视结果
        //只关心读写，那fsm进入STATE_T就没法往下推了，判断STATE_AUTO让其往下推
        //如果fsm12.sfd可读与fsm12.dfd可写两件事同时发生，那只能处理其中一件？
        //如果fsm12.state是STATE_R，但只发生可写，那fsm_driver(&fsm12)就会阻塞read()?
        if(pfd[0].revents & POLLIN || pfd[1].revents & POLLOUT || fsm12.state > STATE_AUTO){
            fsm_driver(&fsm12);
        }
        if(pfd[1].revents & POLLIN || pfd[0].revents & POLLOUT || fsm21.state > STATE_AUTO){
            fsm_driver(&fsm21);
        }
#endif
    }

    fcntl(fd1, F_SETFL, fd1_save);
    fcntl(fd2, F_SETFL, fd2_save);
}

int main(int argc, char **argv)
{
    int fd1, fd2;

    if(argc != 1){
        fprintf(stderr, "argc !=1");
        usage();
        exit(1);
    }

    fd1 = open(TTY1, O_RDWR);
    if(fd1 < 0){
        perror("open()");
        exit(1);
    }
    write(fd1, "TTY1\n", 5);


    fd2 = open(TTY2, O_RDWR|O_NONBLOCK);
    if(fd2 < 0){
        perror("open()");
        close(fd1);
        exit(1);
    }
    write(fd2, "TTY2\n", 5);

    relay(fd1, fd2);

    close(fd2);
    close(fd1);
    return 0;
}

static void usage(void)
{
#define USAGE "Usage:\n\
  Helloworld\n"
    printf(USAGE);
}
